const std = @import("std");
const Order = std.math.Order;

const PipeType = @import("pipe.zig").PipeType;
const Pipe = @import("pipe.zig").Pipe;
const Vector = @import("vector.zig").Vector;


pub const Pipes = struct {
	allocator: std.mem.Allocator = std.heap.page_allocator,
	_pipes: std.AutoHashMap(u16, Pipe) = undefined,
	_grid_width: u8 = undefined,


	pub fn init(self: *Pipes, grid_width: u8) void {
		self._pipes = std.AutoHashMap(u16, Pipe).init(self.allocator);
		self._grid_width = grid_width;
	}


	pub fn deinit(self: *Pipes) void {
		self._pipes.deinit();
	}


	pub fn add_pipe(self: *Pipes, x: u8, y: u8, pipe_type: PipeType) !void {
		const key: u16 = self.calc_key(x, y);
		const pipe = Pipe{.x = x, .y = y, .type = pipe_type};
		try self._pipes.put(key, pipe);
	}


	pub fn get_pipe(self: *Pipes, x: u8, y: u8) ?*Pipe {
		const key: u16 = self.calc_key(x, y);
		return self._pipes.getPtr(key);
	}


	pub fn move_pipe(self: *Pipes, pipe: *Pipe, new_x: u8, new_y: u8) !*Pipe {
		if (pipe.is_end()) {
			return pipe;
		}

		var new_pipe: Pipe = pipe.*;
		new_pipe.x = new_x;
		new_pipe.y = new_y;
		if (self._pipes.remove(self.calc_key(pipe.x, pipe.y))) {
			const new_key: u16 = self.calc_key(new_x, new_y);
			try self._pipes.put(new_key, new_pipe);
			return self._pipes.getPtr(new_key).?;
		}

		return pipe;
	}


	pub fn get_connected_pipes(self: *Pipes, pipe: *const Pipe) [4]?*Pipe {
		const adjacent: [4]?*Pipe = .{
			self.get_pipe(pipe.x, pipe.y -| 1),
			self.get_pipe(pipe.x, pipe.y +| 1),
			self.get_pipe(pipe.x -| 1, pipe.y),
			self.get_pipe(pipe.x +| 1, pipe.y),
		};

		var connected: [4]?*Pipe = .{null, null, null, null};
		for (0..adjacent.len) |idx| {
			if (
					adjacent[idx] != null
					and Pipes.check_connected(pipe, adjacent[idx].?)
			) {
				connected[idx] = adjacent[idx];
			}
		}
		return connected;
	}


	pub fn check_connected(pipe1: *const Pipe, pipe2: *const Pipe) bool {
		const pipe1_pos = Vector{.x = pipe1.x, .y = pipe1.y};
		const pipe2_pos = Vector{.x = pipe2.x, .y = pipe2.y};
		var pipe1_to_pipe2 = false;
		var pipe2_to_pipe1 = false;

		for (pipe1.get_connections()) |conn| {
			if (conn == null) { continue; }

			if (std.meta.eql(pipe2_pos, conn.?)) {
				pipe1_to_pipe2 = true;
				break;
			}
		}

		if (pipe1_to_pipe2) {
			for (pipe2.get_connections()) |conn| {
				if (conn == null) { continue; }

				if (std.meta.eql(pipe1_pos, conn.?)) {
					pipe2_to_pipe1 = true;
					break;
				}
			}
		}

		return pipe1_to_pipe2 and pipe2_to_pipe1;
	}


	pub fn iterator(self: *Pipes) std.AutoHashMap(u16, Pipe).Iterator {
		return self._pipes.iterator();
	}


	pub fn pipe_cmp(context: void, a: *Pipe, b: *Pipe) Order {
		_ = context;
		_ = a;
		_ = b;
		return Order.eq;
	}


	fn calc_key(self: *Pipes, x: u8, y: u8) u16 {
		return x + @as(u16, y) * self._grid_width;
	}
};
